package net.cyndeline.rldungeon.dgs.strategy.pointlessArea

import net.cyndeline.rldungeon.dgs.graph.{DGSFunctionality, Room}
import scalax.collection.GraphEdge.UnDiEdge
import scalax.collection.GraphPredef.OuterEdge
import net.cyndeline.rldungeon.grammar.Strategy
import scalax.collection.immutable.Graph
import net.cyndeline.rldungeon.dgs.{ActivatorResponderAmountEstimator, EdgeCopyFactory, RewardProcessor, ActivatorResponderProcessor}
import scala.util.Random
import com.escalatesoft.subcut.inject.{BindingModule, Injectable}
import net.cyndeline.rldungeon.dgs.strategy.pointlessArea.algorithms._
import scala.reflect.runtime.universe._
import scala.reflect._
import net.cyndeline.rldungeon.dgs.strategy.help.{CollapsedEdge, CollapsedNode, SuperNodeFactory, SuperNodeFactoryInterface}
import net.cyndeline.rldungeon.dgs.strategy.pointlessArea.help.{PointlessAreaDataSetter, PointlessAreaDataSetterI}

/**
 * The main object in charge of removing pointless areas.
 *
 * Modifies a graph with a finished topology by adding objects to its pointless areas, making them more attractive or
 * mandatory for the player to explore.
 */
//TODO write an integration test for this class
class PointlessAreaStrategy(implicit val bindingModule: BindingModule) extends PointlessAreaReductionStrategy with Injectable {

  /* Marks rooms as pointless and adds PointlessAreaData objects. */
  val pointlessSetter = injectOptional[PointlessAreaDataSetterI] getOrElse { new PointlessAreaDataSetter() }

  /* Computes pointless areas. */
  val pointlessAreaDesignator = injectOptional[PointlessAreaDesignatorI] getOrElse { new PointlessAreaDesignator() }

  /**
   * Produces a graph representation of a map.
   *
   * @param initialGraph The graph that the productions initially will be applied to.
   * @param cycleCollapsedGraph Cycle-collapsed version of the initial graph, where every vertex is represented by
   *                            a collapsed node.
   * @param start Start vertex in the initial graph.
   * @param goal Stop vertex in the initial graph.
   * @param random Random object used to select responder amounts.
   * @param activatorResponderProcessor Modifies rooms and corridors by adding responders and activators to them.
   * @param rewardProcessor Modifies rooms by adding rewards to them.
   * @param edgeFactory Creates new edges from a pair of vertices.
   * @param responderAmount Responsible for computing the amount of responders that should be placed on a map.
   * @return the finished map graph if one was possible to produce according to the strategy, otherwise None.
   */
  def apply[VType <: Room[VType]: ClassTag : TypeTag, EType[X] <: UnDiEdge[X] with DGSFunctionality[X, EType] with OuterEdge[X, EType]]
    (initialGraph: Graph[VType, EType],
     cycleCollapsedGraph: Graph[CollapsedNode[VType], CollapsedEdge[VType]#Inner],
     start: VType,
     goal: VType,
     random: Random,
     activatorResponderProcessor: ActivatorResponderProcessor[VType, EType],
     rewardProcessor: RewardProcessor[VType, EType],
     edgeFactory: EdgeCopyFactory[VType, EType],
     responderAmount: ActivatorResponderAmountEstimator[VType, EType]): Option[Graph[VType, EType]] = {

    val areaRemover = injectOptional[PointlessAreaAlgorithm] getOrElse {
      new PointlessAreaRemover()
    }

    /* Since the graph is now a tree, there should only be one path from start to goal. */
    val startToGoalPath: List[CollapsedNode[VType]] = findCollapsedPath(findNode(start, cycleCollapsedGraph), findNode(goal, cycleCollapsedGraph), cycleCollapsedGraph)

    /* Every room outside the main start -> goal path is considered pointless. */
    val pointlessAreas = pointlessAreaDesignator.computePointlessAreas(cycleCollapsedGraph, startToGoalPath)
    val pointlessDataResult = pointlessSetter.addPointlessAreaDataAndStatus(initialGraph, cycleCollapsedGraph, pointlessAreas, edgeFactory)
    val pointlessRegularGraph = pointlessDataResult.graph
    val pointlessCollapsedGraph = pointlessDataResult.collapsedGraph
    val respRanges = responderAmount.computeResponderAmount(pointlessRegularGraph)

    val respondersOnMainPath = random.nextInt(respRanges.maximumResponderAmountOnMainPath - respRanges.minimumResponderAmountOnMainPath) + respRanges.minimumResponderAmountOnMainPath
    val respondersOutsideMainPath = random.nextInt(respRanges.maximumResponderAmountOutsideMainPath - respRanges.minimumResponderAmountOutsideMainPath) + respRanges.minimumResponderAmountOutsideMainPath

    val collapsedStart = findNode(start, pointlessCollapsedGraph)
    val collapsedGoal = findNode(goal, pointlessCollapsedGraph)

    Option(areaRemover.apply(pointlessRegularGraph, pointlessCollapsedGraph, collapsedStart, collapsedGoal, respondersOnMainPath, respondersOutsideMainPath, RemoverSetupData(activatorResponderProcessor, rewardProcessor, edgeFactory, random)))
  }

  private def findCollapsedPath[VType <: Room[VType], EType[X] <: UnDiEdge[X] with DGSFunctionality[X, EType] with OuterEdge[X, EType]]
    (start: CollapsedNode[VType], goal: CollapsedNode[VType], graph: Graph[CollapsedNode[VType], CollapsedEdge[VType]#Inner]): List[CollapsedNode[VType]] = {
    val s = graph.get(start)
    val g = graph.get(goal)

    val path = s.pathTo(g).getOrElse {
      throw new Error("No path from start " + start + " to goal " + goal + " found.")
    }

    val pathList: List[CollapsedNode[VType]] = (for {
      node <- path.nodes
      n: CollapsedNode[VType] = node
    } yield n).toList

    pathList
  }

  private def findNode[VType <: Room[VType], EType[X] <: UnDiEdge[X] with DGSFunctionality[X, EType] with OuterEdge[X, EType]]
    (representedVertex: VType, graph: Graph[CollapsedNode[VType], CollapsedEdge[VType]#Inner]): CollapsedNode[VType] = {
    graph.nodes.find(n => n.contains(representedVertex)).getOrElse {
      throw new Error("The vertex " + representedVertex + " was not found in the collapsed graph " + graph)
    }
  }
}
